#ifndef LINKEDOBJLIST_H
#define LINKEDOBJLIST_H

#ifndef CORE_H
# include "Core.h"
#endif

namespace System {

	template <typename T>
	class LinkedObjList;

	template <typename T>
	class LinkedObjListNode
	{
	friend class LinkedObjList<T>;
	private:
        LinkedObjList<T>* _list;
        LinkedObjListNode<T>* _next;
        LinkedObjListNode<T>* _prev;

	public:
        __forceinline __device__ void Invalidate()
        {
            _list = nullptr;
            _next = nullptr;
            _prev = nullptr;
        }

        __forceinline __device__ LinkedObjList<T>* getList() { trace(LinkedObjListNode, "getList"); return _list; }
        __forceinline __device__ T* getNext() { return (T*)(_next != nullptr && _next != _list->_head ? _next : nullptr); }
		__forceinline __device__ T* getPrevious() { return (T*)(_prev != nullptr && _prev != _list->_head ? _prev : nullptr); }
	};

	template <typename T>
	class LinkedObjList
	{
	friend class LinkedObjListNode<T>;
	private:
        LinkedObjListNode<T>* _head;
        int _count;
        int _version;

		__device__ void InternalInsertNodeBefore(LinkedObjListNode<T>* node, LinkedObjListNode<T>* newNode)
        {
            newNode->_next = node;
            newNode->_prev = node->_prev;
            node->_prev->_next = newNode;
            node->_prev = newNode;
            _version++;
            _count++;
        }

        __device__ void InternalInsertNodeToEmptyList(LinkedObjListNode<T>* newNode)
        {
            newNode->_next = newNode;
            newNode->_prev = newNode;
            _head = newNode;
            _version++;
            _count++;
        }

        __device__ void InternalRemoveNode(LinkedObjListNode<T>* node)
        {
            if (node->_next == node)
                _head = nullptr;
            else
            {
                node->_next->_prev = node->_prev;
                node->_prev->_next = node->_next;
                if (_head == node)
                    _head = node->_next;
            }
            node->Invalidate();
            _count--;
            _version++;
        }

		__device__ void ValidateNewNode(LinkedObjListNode<T>* node)
        {
            if (node == nullptr)
                thrownew(ArgumentNullException, "node");
            if (node->_list != nullptr)
                thrownew(InvalidOperationException, "LinkedListNodeIsAttached");
        }

        __device__ void ValidateNode(LinkedObjListNode<T>* node)
        {
            if (node == nullptr)
                thrownew(ArgumentNullException, "node");
            if (node->_list != this)
                thrownew(InvalidOperationException, "ExternalLinkedListNode");
        }

	public:
		__device__ void xtor(fallocContext* fallocCtx)
		{
			_head = nullptr;
			_version = 0;
			_count = 0;
		}

		__device__ void AddAfter(LinkedObjListNode<T>* node, LinkedObjListNode<T>* newNode)
        {
            ValidateNode(node);
            ValidateNewNode(newNode);
            InternalInsertNodeBefore(node->_next, newNode);
            newNode->_list = this;
        }

        __device__ void AddBefore(LinkedObjListNode<T>* node, LinkedObjListNode<T>* newNode)
        {
            ValidateNode(node);
            ValidateNewNode(newNode);
            InternalInsertNodeBefore(node, newNode);
            newNode->_list = this;
            if (node == _head)
                _head = newNode;
        }

        __device__ void AddFirst(LinkedObjListNode<T>* node)
        {
            ValidateNewNode(node);
            if (_head == nullptr)
                InternalInsertNodeToEmptyList(node);
            else
            {
                InternalInsertNodeBefore(_head, node);
                _head = node;
            }
            node->_list = this;
        }

        __device__ void AddLast(LinkedObjListNode<T>* node)
        {
            ValidateNewNode(node);
            if (_head == nullptr)
                InternalInsertNodeToEmptyList(node);
            else
                InternalInsertNodeBefore(_head, node);
            node->_list = this;
        }

        __device__ void Clear()
        {
            LinkedObjListNode<T>* head = _head;
            while (head != nullptr)
            {
                LinkedObjListNode<T>* node2 = head;
                head = (LinkedObjListNode<T>*)head->getNext();
                node2->Invalidate();
            }
            _head = nullptr;
            _count = 0;
            _version++;
        }

        __device__ void Remove(LinkedObjListNode<T>* node)
        {
            ValidateNode(node);
            InternalRemoveNode(node);
        }

        __device__ void RemoveFirst()
        {
            if (_head == nullptr)
                thrownew(InvalidOperationException, "LinkedListEmpty");
            InternalRemoveNode(_head);
        }

        __device__ void RemoveLast()
        {
            if (_head == nullptr)
                thrownew(InvalidOperationException, "LinkedListEmpty");
            InternalRemoveNode(_head->_prev);
        }

        __device__ int getCount() { return _count; }
        __device__ T* getFirst() {  return (T*)_head;  }
        __device__ T* getLast()  {  return (T*)(_head != nullptr ? _head->_prev : nullptr);   }

		#pragma region Enumerator

		//////////////////////////
		// ENUMERATOR
        typedef struct Enumerator_t
        {
		private:
            //LinkedObjList<T>* _list;
            LinkedObjListNode<T>* _node;
            int _version;
            int _index;

		public:
            T* Current;

			__device__ void xtor(LinkedObjList<T>* list)
			{
				//_list = list;
				_version = list->_version;
				_node = list->_head;
				Current = nullptr;
				_index = 0;
			}
			__device__ void Dispose() { }

			__device__ bool MoveNext(LinkedObjList<T>* list)
			{
				if (_version != list->_version)
					thrownew(InvalidOperationException, "InvalidOperation_EnumFailedVersion");
				if (_node == nullptr)
				{
					_index = list->getCount() + 1;
					return false;
				}
				_index++;
				Current = (T*)_node;
				_node = _node->_next;
				if (_node == list->_head)
					_node = nullptr;
				return true;
			}

			__device__ void Reset(LinkedObjList<T>* list)
			{
				if (_version != list->_version)
					thrownew(InvalidOperationException, "InvalidOperation_EnumFailedVersion");
				Current = nullptr;
				_node = list->_head;
				_index = 0;
			}
		} Enumerator;

		__forceinline __device__ void GetEnumerator(Enumerator& t)
        {
			trace(LinkedObjList_Enumerator, "GetEnumerator %d", this);
			t.xtor(this);
        }

        #pragma endregion
	};

}

#endif // LINKEDOBJLIST_H